from selenium import webdriver
# 鼠标动作
from selenium.webdriver.common.action_chains import ActionChains
import requests
import time
import cv2
import random
import numpy as np

class DouBanLogin:
    def __init__(self,url):
        self.url = url
        self.driver = webdriver.Chrome(r'E:\chromedriver_win32 存放目录\chromedriver.exe')

    # 下载图片到本地
    def get_image(self,img_url, imgname):
        # 以流的形式下载文件
        image = requests.get(img_url, stream=True)
        # str.join()方法用于将序列中的元素以指定的字符(str)连接生成一个新的字符串
        imgName = ''.join(["./", imgname])
        with open(imgName, 'wb') as f:
            for chunk in image.iter_content(chunk_size=1024):  # 循环写入  chunk_size：每次下载的数据大小
                if chunk:
                    f.write(chunk)
                    f.flush()
            f.close()

    # 使用opencv模块 计算缺口的偏移值
    def get_image_offset(self,background_image_url, slider_image_url):
        back_image = 'back_image.png'  # 背景图像命名
        slider_image = 'slider_image.png'  # 滑块图像命名
        self.get_image(background_image_url, back_image)
        self.get_image(slider_image_url, slider_image)
        # 获取图片并灰度化
        block = cv2.imread(slider_image, 0)
        template = cv2.imread(back_image, 0)
        w, h = block.shape[::-1]
        print(w, h)
        # 二值化后图片名称
        block_name = 'block.jpg'
        template_name = 'template.jpg'
        # 保存二值化后的图片
        cv2.imwrite(block_name, block)
        cv2.imwrite(template_name, template)
        block = cv2.imread(block_name)
        block = cv2.cvtColor(block, cv2.COLOR_RGB2GRAY)
        block = abs(255 - block)
        cv2.imwrite(block_name, block)
        block = cv2.imread(block_name)
        template = cv2.imread(template_name)
        # 获取偏移量
        # 模板匹配，查找block在template中的位置，返回result是一个矩阵，是每个点的匹配结果
        result = cv2.matchTemplate(block, template, cv2.TM_CCOEFF_NORMED)
        x, y = np.unravel_index(result.argmax(), result.shape)
        print(x, y)
        # 由于获取到的验证码图片像素与实际的像素有差(实际：280*158 原图：680*390)，故对获取到的坐标进行处理
        offset = y * (280 / 680)
        # 画矩形圈出匹配的区域
        # 参数解释：1.原图 2.矩阵的左上点坐标 3.矩阵的右下点坐标 4.画线对应的rgb颜色 5.线的宽度
        cv2.rectangle(template, (y, x), (y + w, x + h), (7, 249, 151), 2)
        self.show(template)
        return offset

    # 显示图片
    def show(self,name):
        cv2.imshow('Show', name)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

    # 采用物理加速度位移相关公式按照先快后慢的人工滑动规律进行轨迹计算，
    # 同时还采用了模拟人滑动超过了缺口位置再滑回至缺口的情况以使轨迹更契合人工滑动轨迹
    def get_track(self,distance):
        track = []
        current = 0
        mid = distance * 3 / 4
        t = random.randint(2, 3) / 10
        v = 0
        while current < distance:
            if current < mid:
                a = 2
            else:
                a = -3
            v0 = v
            v = v0 + a * t
            move = v0 * t + 1 / 2 * a * t * t
            current += move
            track.append(round(move, 2))
        return track

    def login(self): #实现主要逻辑
        self.driver.get(self.url)
        time.sleep(2)
        # 1.切换到iframe框架
        self.driver.switch_to.frame(self.driver.find_element_by_tag_name('iframe'))
        # 2.切换登录方式：点击用账号密码登录的按钮，不然会找不到输入账号和密码的地方
        # self.driver.find_element_by_class_name("account-tab-account on").click()
        self.driver.find_element_by_xpath('/html/body/div[1]/div[1]/ul[1]/li[2]').click()
        # 3.输入账户密码
        username = self.driver.find_element_by_id("username")
        username.clear()
        username.send_keys("13759903699")
        password = self.driver.find_element_by_id("password")
        password.clear()
        password.send_keys("153db3418")

        # 4.处理滑块验证码
        # 4.1获取滑块验证码图片，并下载到本地
        # 4.1.1获取背景图和滑块图的url
        background_image_url = self.driver.find_elements_by_id('slideBkg')
        if len(background_image_url)>0:
            background_image_url = background_image_url[0].get_attribute('src')
            slider_image_url = self.driver.find_element_by_id('slideBlock').get_attribute('src')
            # 4.1.2下载图片到本地
            # 4.2使用opencv模块中的"模板匹配"方法获取缺口的位置
            # 4.3模拟运动轨迹
            # 4,4selenium具体操作
            # 定位到滑块按钮
            button = self.driver.find_element_by_id('tcaptcha_drag_thumb')
            # 拖动操作用到ActionChains类，实例化
            action = ActionChains(self.driver)
            # perform()用来执行ActionChains中存储的行为
            action.click_and_hold(on_element=button).perform()
            # 清除之前的action
            action.reset_actions()
            # 获取轨迹
            distance = self.get_image_offset(background_image_url,slider_image_url)
            track = self.get_track(distance + random.randint(3, 5))
            print(track)
            sum = 0
            for i in track:
                sum += i
            print(sum)
            for i in track:
                action.move_by_offset(xoffset=i, yoffset=0).perform()
                action.reset_actions()
            time.sleep(1)
            action.release().perform()

        # 5.点击登录（以下三种方法均可行）
        # botton = self.driver.find_element_by_xpath('//div[@class="account-form"]/div[last()]/a ')
        # # 这里需要注意，当元素的class属性有好几个的时候，此函数的参数填class的第一个就好
        # botton = self.driver.find_element_by_class_name('btn') # 元素的class属性：btn btn-account
        botton = self.driver.find_element_by_class_name('account-form-field-submit ') #可以搜索“javascript:;”找到js文件，进一步找到真正标签及其属性
        botton.click()

        # 6.获取cookie
        cookies = {i["name"]:i["value"] for i in self.driver.get_cookies()}
        print(cookies)

        time.sleep(3)
        self.driver.quit()

if __name__ == '__main__':
    login_url = "https://www.douban.com/"
    douban_login = DouBanLogin(login_url)
    douban_login.login()

"""
注意1：30小时…/Scrapy框架/douban/douban/spider/login,豆瓣登录问题

重点：
    “selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="username"]"}”
    1.要先切换到子框架
    2.要先点击用账号密码登录的按钮，不然会找不到输入账号和密码的地方
"""